﻿#region Header

// Majesty of Omega - The next evolution of macro management space strategy
// 
// Copyright 2007-2009 André Claaßen
// 
// This file is protected by the terms and conditions of the
// Microsoft Community License (Ms-CL 1.1), a copy of which should
// have been distributed along with this software. If not,
// you may find the license information at the following URL:
// 
// http://www.microsoft.com/resources/sharedsource/licensingbasics/communitylicense.mspx

#endregion

#region

using System.Collections.Generic;
using DesignByContract;
using Majesty_of_Omega.Logic.GameConfiguration;
using Majesty_of_Omega.Model.Game;
using Majesty_of_Omega.Model.Space;
using Majesty_of_Omega.Util;
using Majesty_of_Omega.Properties;

#endregion

namespace Majesty_of_Omega.Logic.GameConfiguration
{
    /// <summary>
    /// This class searsches new start positiones
    /// </summary>
    public class StartPositionSearcher
    {
        private const int MinimumDistanceFromOtherPlayers = 3;

        /// <summary>
        /// This positions will be generated
        /// </summary>
        private readonly List<StartPosition> _listStartPositions = new List<StartPosition>();

        /// <summary>
        /// Random traverse through the stars 
        /// </summary>
        private readonly RandomPlayer _randomPlayer;

        /// <summary>
        /// Search positions for the game
        /// </summary>
        private readonly SaveGame _saveGame;

        /// <summary>
        /// Search start positions for the given game
        /// </summary>
        /// <param name="saveGame">the new game</param>
        public StartPositionSearcher(SaveGame saveGame)
        {
            Require.IsNotNull(saveGame);
            Require.IsNotNull(saveGame.Galaxy);
            Require.IsTrue(saveGame.Galaxy.Stars.Count > 1);
            Require.IsTrue(saveGame.Galaxy.StarLanes.Count > 1);
            Require.IsNotNull(saveGame.playerList);
            Require.IsNotNull(saveGame.playerList.Count > 0);
            _saveGame = saveGame;
            _randomPlayer = new RandomPlayer(3434, _saveGame.Galaxy.Stars.Count);
        }

        ///<summary>
        /// This function generates fair start positions
        ///</summary>
        ///<returns>list of start positions </returns>
        public List<StartPosition> GenerateStartPositions()
        {
            foreach (Player player in _saveGame.playerList)
            {
                CreateNewStartPositionForPlayer(player);
            }
            Ensure.AreEqual(_listStartPositions.Count, _saveGame.playerList.Count);
            return _listStartPositions;
        }

        /// <summary>
        /// Creates the new start position for a player.
        /// </summary>
        /// <param name="player">The player.</param>
        private void CreateNewStartPositionForPlayer(Player player)
        {
            StartPosition startPosition = new StartPosition();
            startPosition.player = player;
            int distanceToOtherPlayers = 0;
            while (distanceToOtherPlayers < MinimumDistanceFromOtherPlayers)
            {
                if (_randomPlayer.RemainingRandoms == 0)
                {
                    throw new GameGenerationException(
                        string.Format(
                            Resources.SearchForPlayersStartingPositionsAbortedNotEnoughRoomFor0PlayersInA1Galaxy,
                            _saveGame.playerList.Count, _saveGame.Galaxy.Stars.Count));
                }
                startPosition.star = _saveGame.Galaxy.Stars[_randomPlayer.GetNextRandomItem()];
                distanceToOtherPlayers = CalcMinimumDistanceToOtherPlayers(startPosition.star);
            }
            _listStartPositions.Add(startPosition);
        }

        /// <summary>
        /// This function calculates the minimum distance between the selected position (as star) and the 
        /// starting position from other players
        /// </summary>
        private int CalcMinimumDistanceToOtherPlayers(Star newStar)
        {
            int minimumDistanceToOtherPlayers = int.MaxValue;
            foreach (StartPosition position in _listStartPositions)
            {
                int distance = CalcMinimumDistanceInJumps(null, newStar, position.star);
                if (distance < minimumDistanceToOtherPlayers)
                    minimumDistanceToOtherPlayers = distance;
            }
            return minimumDistanceToOtherPlayers;
        }

        /// <summary>
        /// Calcs the minimal distance and the optimal route between two stars in jumps
        /// </summary>
        /// <param name="visitedStarLanes">The visited star lanes.</param>
        /// <param name="starStart">The star start.</param>
        /// <param name="starDestination">The star destination.</param>
        /// <returns>optiomal route between the stars in jumps</returns>
        public static int CalcMinimumDistanceInJumps(Stack<StarLane> visitedStarLanes, Star starStart,
                                                     Star starDestination)
        {
            int minimumDistanceInJumps = int.MaxValue;
            if (starStart == starDestination)
                return 0;

            if (visitedStarLanes == null)
                visitedStarLanes = new Stack<StarLane>();

            foreach (StarLane lane in starStart.StarLanes)
            {
                if (visitedStarLanes.Contains(lane)) continue;

                visitedStarLanes.Push(lane);
                Star oppositeStar = lane.GetOppositeStar(starStart);
                int distance = CalcMinimumDistanceInJumps(visitedStarLanes, oppositeStar, starDestination);
                if (distance < int.MaxValue)
                    distance++;
                if (distance < minimumDistanceInJumps)
                    minimumDistanceInJumps = distance;
                visitedStarLanes.Pop();
            }
            return minimumDistanceInJumps;
        }

        #region Nested type: StartPosition

        /// <summary>
        /// a calculated start position for a player. the position is determined by a star 
        /// from the games galaxy
        /// </summary>
        public struct StartPosition
        {
            /// <summary>
            /// Start position of this player
            /// </summary>
            public Player player;

            /// <summary>
            /// the home star of the player
            /// </summary>
            public Star star;
        }

        #endregion
    }
}